Skip to content

[rush] Add PNPM global virtual store support for rush install#5830

Open
EscapeB wants to merge 6 commits into
microsoft:mainfrom
EscapeB:feat/support_pnpm_global_virtual_store
Open

[rush] Add PNPM global virtual store support for rush install#5830
EscapeB wants to merge 6 commits into
microsoft:mainfrom
EscapeB:feat/support_pnpm_global_virtual_store

Conversation

@EscapeB

@EscapeB EscapeB commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds Rush support for PNPM's global virtual store for workspace installs.

This targets workflows where multiple Git worktrees of the same monorepo are created and deleted frequently, especially AI-assisted development workflows where several agents may work in parallel. Without this, each worktree may recreate a large local virtual store under common/temp/node_modules/.pnpm, making worktree setup and cleanup expensive.

When enabled, Rush writes enableGlobalVirtualStore: true to the generated pnpm-workspace.yaml. PNPM then stores package instance folders under the shared PNPM store instead of recreating them inside every worktree.

Existing repositories keep their current behavior by default. The feature is enabled per machine/session with:

RUSH_PNPM_ENABLE_GLOBAL_VIRTUAL_STORE=1

Details

This PR intentionally does not add a new pnpm-config.json option. The setting is controlled by an environment variable so all subspaces use the same behavior for a given install.

When RUSH_PNPM_ENABLE_GLOBAL_VIRTUAL_STORE=1 is set, Rush validates that:

  • PNPM is version 10.12.1 or newer.
  • The PNPM store is shared, either via "pnpmStore": "global" or RUSH_PNPM_STORE_PATH.
  • usePnpmSyncForInjectedDependencies is not enabled, because pnpm-sync currently assumes the virtual store lives under node_modules/.pnpm.

Store selection keeps the existing Rush behavior:

  • With RUSH_PNPM_STORE_PATH, Rush passes that path to PNPM.
  • With "pnpmStore": "global" and no RUSH_PNPM_STORE_PATH, Rush lets PNPM use its default global store location.
  • With "pnpmStore": "local" and no RUSH_PNPM_STORE_PATH, enabling global virtual store is rejected because the store would still be worktree-local.

If RUSH_PNPM_STORE_PATH points inside the repo, Rush prints a warning instead of failing, since the install can still work but will not reduce setup or cleanup cost across worktrees.

Rush also records the global virtual store setting in the last-install flag. If RUSH_PNPM_ENABLE_GLOBAL_VIRTUAL_STORE or the effective PNPM store path changes between installs, Rush reports an error and asks for rush install --purge / rush update --purge instead of silently mixing install layouts.

With PNPM 10.12.1+, common/temp/node_modules/.pnpm may still exist, but it only contains lightweight metadata such as lock.yaml and node_modules. Package instance folders are stored under the shared PNPM store.

Testing

Ran the Rush lib test suite:

node common/scripts/install-run-rush.js test --to @microsoft/rush-lib --verbose

This covers environment variable parsing, workspace file generation, validation errors, last-install flag mismatch detection, and PNPM store argument behavior.

Added a PNPM global virtual store integration test that creates a temporary Rush repo, enables the feature with RUSH_PNPM_ENABLE_GLOBAL_VIRTUAL_STORE=1, sets RUSH_PNPM_STORE_PATH, then runs:

rush update
rush install
rush build

The test verifies the generated workspace file, shared PNPM store usage, dependency links, and built output execution.

Also ran:

cd build-tests/rush-package-manager-integration-test
npm run build
npm run test

Result: npm, PNPM global virtual store, and yarn integration tests all passed.

Question

build-tests/rush-package-manager-integration-test previously only covered npm and yarn, and its package description referred to "non-pnpm package managers." I added the PNPM case there because this feature needs real install-structure validation. Please confirm whether this is the right place for that coverage.

@EscapeB EscapeB force-pushed the feat/support_pnpm_global_virtual_store branch from 5f2c85a to 7a0be79 Compare June 11, 2026 13:13
@EscapeB

EscapeB commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Hi, @octogonz @iclanton, could you please take some time to review this PR and see if there are any issues?

@octogonz

Copy link
Copy Markdown
Collaborator

I am at the JSNation conference this week, will take a look later. Thanks!

@iclanton iclanton moved this from Needs triage to In Progress in Bug Triage Jun 15, 2026
Comment thread common/config/rush/pnpm-config.json Outdated
* It is not currently compatible with the
* `usePnpmSyncForInjectedDependencies` experiment.
*
* PNPM documentation: https://pnpm.io/settings#enableglobalvirtualstore

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to these docs:

If pnpm detects that it is running in CI, this setting is automatically disabled.

🤔 I'm a bit curious how that works. It sounds like it could be nondeterministic "magic" that could make it difficult to repro bugs. If so, Rush usually tries to find a way to disable these behaviors, or replace them with some documented Rush mechanism.

@EscapeB EscapeB Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://pnpm.io/cli/install#--frozen-lockfile

According to "install" command doc, seems this package(ci-info) was used to detect CI environment
image

Comment thread common/config/rush/pnpm-config.json Outdated
Comment thread libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json Outdated
Comment thread libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts Outdated
`${this.rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}`,
rushJsonFolder: this.rushConfiguration.rushJsonFolder,
pnpmStore: this.rushConfiguration.pnpmOptions.pnpmStore,
pnpmStorePath: this.rushConfiguration.pnpmOptions.pnpmStorePath,

@octogonz octogonz Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.rushConfiguration.pnpmOptions.pnpmStore reads from common/config/rush/pnpm-config.json, however each subspace can have its own pnpm-config.json. Each of these files can have its own enableGlobalVirtualStore setting, which complicates the consistency check being performed here.

It seems to me that enableGlobalVirtualStore is probably a machine-specific setting that does NOT affect the pnpm-lockfile.yaml (@EscapeB could you confirm this?). In other words, it determines how where the node_modules symlinks point, but does NOT affect the semantics of the installation plan. If so, then it is very similar to RUSH_PNPM_STORE_PATH.

🚩 If it is a personal preference, then maybe it should not be represented as a setting in pnpm-config.json, as this file is tracked by Git and affects everyone who works in the monorepo. Compare with this alternative design:

  • If the local machine defines RUSH_PNPM_STORE_GLOBAL=1, then RUSH_PNPM_STORE_PATH will be used as the global virtual store. (If RUSH_PNPM_STORE_PATH is empty or unset, then we use PNPM's default "global" store location.)
  • rush install implements it by emitting enableGlobalVirtualStore=true into temp/pnpm-workspace.yaml
  • Rush (or PNPM) validates that the environment variables don't cause problems or nondeterminism:
    • Print an error is RUSH_PNPM_STORE_GLOBAL is used when PNPM version < 10.12.1.
    • Extend the RUSH_PNPM_STORE_PATH validation so that an error is reported if RUSH_PNPM_STORE_GLOBAL or RUSH_PNPM_STORE_PATH has changed since the last rush install. (We don't silently "heal", because this is specifically a performance setting, and it's a real performance problem if different commands are invoked with conflicting environments.)

@EscapeB EscapeB Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that enableGlobalVirtualStore is probably a machine-specific setting that does NOT affect the pnpm-lockfile.yaml

Yes, according to pnpm docs, pnpm-lockfile.yaml won't change, only symlink structure under node_modules changes.

I didn't reliazed there are multiple subspaces and will have multiple pnpm-config.json, I agreed that environment var will be a better approach.

node: process.versions.node
});

if (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR also avoids acquiring the global package manager install lock when the requested package manager is already installed and no matching install lock file exists. If a lock file exists, Rush still acquires the lock to preserve stale/dirty recovery behavior.

@EscapeB this secondary fix is "small", but its review will be entirely focused on proving there aren't any race conditions. Could you extract it into a separate PR so it is easier to analyze? This will also decouple the two fixes, so we can merge+publish one earlier if the other one takes longer to review. 🙏

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will create another PR for this small fix.

@EscapeB

EscapeB commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

I extracted the package-manager install lock optimization into a separate PR: #5844

This PR has been updated to remove the InstallHelpers changes and now only contains the PNPM global virtual store work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants